//
//  main.m
//  方法签名
//
//  Created by 林域 on 2021/9/1.
//  Copyright © 2021 aaaa. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "TestObj.h"

void demo01(void);
void demo02(void);

int main(int argc, const char * argv[]) {
    
    // methodSignature
    demo01();
    
    // NSInvocation
//    demo02();
    
    return 0;
}

#pragma mark - methodSignature -
void demo01(void) {
    TestObj *obj = [TestObj new];
    NSMethodSignature *s = [obj methodSignatureForSelector:@selector(instanceMethod01:)];
    NSMethodSignature *s1 = [TestObj instanceMethodSignatureForSelector:@selector(instanceMethod02:)];
    NSMethodSignature *s2 = [TestObj methodSignatureForSelector:@selector(classMethod:)];
    
    
    //可以看出 只要参数和类型相同，不管方法名是否一致，都会返回同一个签名对象
    NSLog(@"%p = %p = %p",s,s1,s2);
    
    /**
     返回值，void类型，可以看到编码结果为 v
     
     消息发送底层是通过调用 objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
     根据msg_send函数的传参
     第一个argument为target，id类型，可以看到编码结果为 @
     第二个argument为SEL，可以看到编码结果为 :
     第三个argument开始都是参数，void类型，可以看到编码结果为 v
     */
    NSLog(@"%@",s.debugDescription);
    
    //根据上面的debug可以看出numberOfArguments包含了reciever,sel，和参数。因此这里是3个（Arguments不包含返回值）
    NSLog(@"%lu",s.numberOfArguments);
    NSLog(@"%s",s.methodReturnType);
    NSLog(@"%lu",(unsigned long)s.methodReturnLength);
    
    //可以通过@encode方法查看类型的编码
    NSLog(@"%s",@encode(void));
    NSLog(@"%s",@encode(id));
    NSLog(@"%s",@encode(SEL));
    NSLog(@"%s",@encode(BOOL));
    NSLog(@"%s",@encode(int));
    NSLog(@"%s",@encode(double));
    NSLog(@"=======================================");
}


#pragma mark - 通过NSInvocation调用方法并传参(任意多个参数) -
void demo02(void) {
    TestObj *obj = [TestObj new];
    
//  1、根据函数objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)来看，CTypes只要包含 traget SEL即可成功调用，返回值与参数不一致也无所谓
    NSMethodSignature *signature1 = [NSMethodSignature signatureWithObjCTypes:"i@:"];
    NSInvocation *invocation1 = [NSInvocation invocationWithMethodSignature:signature1];
    invocation1.target = obj;
    invocation1.selector = @selector(testMethod);
    [invocation1 invoke];
    
    //2、参数可传可不传，也可以不用写出来 "i@:"也能正常执行
    NSMethodSignature *signature2 = [NSMethodSignature signatureWithObjCTypes:"i@:i"];
    NSInvocation *invocation2 = [NSInvocation invocationWithMethodSignature:signature2];
    invocation2.target = obj;
    invocation2.selector = @selector(testMethod:);
    [invocation2 invoke];
    
    //3、可以传递多个参数。CTypes中的编码数量要与设置的参数对应，比如CTypes=="i@:i",那么只能设置一个int参数
    // 如果CTypes=="i@:"，那么就不能传参数，⚠️尽量根据方法的实际参数个数把Ctypes写完整。
    NSMethodSignature *signature3 = [NSMethodSignature signatureWithObjCTypes:"i@:id"];
    NSInvocation *invocation3 = [NSInvocation invocationWithMethodSignature:signature3];
    invocation3.target = obj;
    invocation3.selector = @selector(testMethod:digit:);
    NSNumber *intnum = [NSNumber numberWithInt:9];
    NSNumber *doublenum = [NSNumber numberWithDouble:6.00];
    [invocation3 setArgument:&intnum atIndex:2];
    [invocation3 setArgument:&doublenum atIndex:3];
    [invocation3 invoke];
}

#pragma mark -  -
